package com.hjy.backend.transactionManager;

import com.hjy.common.error.Error;
import com.hjy.backend.utils.Panic;
import com.hjy.backend.utils.Parser;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TransactionManagerImpl implements TransactionManager {

    // XID文件头长度
    static final int LEN_XID_HEADER_LENGTH = 8;
    // 每个事务的占用长度
    private static final int XID_FIELD_SIZE = 1;
    // 事务的三种状态
    private static final byte FIELD_TRAN_ACTIVE = 0; // 正在进行，尚未结束
    private static final byte FIELD_TRAN_COMMITTED = 1; // 已提交
    private static final byte FIELD_TRAN_ABORTED = 2; // 已回滚
    // 超级事务，永远为committed状态, 下标为0, 也就是第一个事务
    public static final long SUPER_XID = 0;
    // XID 文件后缀
    static final String XID_SUFFIX = ".xid";


    private RandomAccessFile file; // 对应的 XID 事务文件
    private FileChannel fc;
    private long xidCounter; // 当前事务的个数
    private Lock counterLock;

    TransactionManagerImpl(RandomAccessFile raf, FileChannel fc) {
        this.file = raf;
        this.fc = fc;
        // 创建一个可重入锁
        counterLock = new ReentrantLock();
        checkXIDCounter();
    }


    /**
     * 对 XID 事务文件进行校验, 若有问题就宕机
     * 通过文件头的 8 字节数字反推文件的理论长度，与文件的实际长度做对比。如果不同则认为 XID 文件不合法。
     */
    private void checkXIDCounter() {
        // 文件长度
        long fileLength = 0L;
        try {
            fileLength = file.length();
        } catch (IOException e) {
            Panic.panic(Error.BadXIDFileException);
        }

        if (fileLength < LEN_XID_HEADER_LENGTH) {
            Panic.panic(Error.BadXIDFileException);
        }

        // 给缓冲区分配8个字符
        ByteBuffer buf = ByteBuffer.allocate(LEN_XID_HEADER_LENGTH);

        try {
            // 读取出着8个字符
            fc.position(0);
            fc.read(buf);
        } catch (IOException e) {
            Panic.panic(e);
        }

        // 获取 XID 文件中当前事务的个数
        this.xidCounter = Parser.parseLong(buf.array());
        // 获取最后一个事务应该在文件中的位置
        long end = getXidPosition(this.xidCounter + 1);

        if (end != fileLength) {
            Panic.panic(Error.BadXIDFileException);
        }
    }

    /**
     * 根据事务xid取得其在xid文件中对应的位置
     * xid-1 是因为 xid 0（Super XID） 的状态不需要记录。
     *
     * @param xid : 事务编号
     * @return xid相对位置
     */
    private long getXidPosition(long xid) {
        return LEN_XID_HEADER_LENGTH + (xid - 1) * XID_FIELD_SIZE;
    }

    /**
     * 开启一个事务
     * 首先设置 xidCounter+1 事务的状态为 committed，随后 xidCounter 自增，并更新文件头。
     *
     * @return
     */
    @Override
    public long begin() {
        counterLock.lock();
        try {
            long xid = xidCounter + 1;
            // 修改事务状态为 active
            updateXID(xid, FIELD_TRAN_ACTIVE);
            // 增加事务
            incrXIDCounter();
            return xid;
        } finally {
            counterLock.unlock();
        }
    }

    /**
     * 更新xid事务的状态为status
     *
     * @param xid    事务号
     * @param status 状态
     */
    private void updateXID(long xid, byte status) {
        long offset = getXidPosition(xid);
        byte[] tmp = new byte[XID_FIELD_SIZE];
        tmp[0] = status;

        ByteBuffer buffer = ByteBuffer.wrap(tmp);
        try {
            // 写入状态
            fc.position(offset);
            // 直接覆盖写入, 或者之间到文件末尾写入
            fc.write(buffer);
        } catch (IOException e) {
            Panic.panic(e);
        }

        try {
            // 将缓冲区的数据刷新到磁盘
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    /**
     * 将XID加一，并更新XID Header
     */
    private void incrXIDCounter() {
        xidCounter += 1;
        ByteBuffer buffer = ByteBuffer.wrap(Parser.long2Byte(xidCounter));
        try {
            // 写入状态
            fc.position(0);
            // 直接覆盖写入当前XID个数
            fc.write(buffer);
        } catch (IOException e) {
            Panic.panic(e);
        }

        try {
            // 将缓冲区的数据刷新到磁盘
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        }

    }

    /**
     * 提交事务
     *
     * @param xid
     */
    @Override
    public void commit(long xid) {
        updateXID(xid, FIELD_TRAN_COMMITTED);
    }

    /**
     * 回滚事务
     *
     * @param xid
     */
    @Override
    public void abort(long xid) {
        updateXID(xid, FIELD_TRAN_ABORTED);
    }

    /**
     * 检测该事务是否处于status状态
     *
     * @param xid    xid 事务相对位置
     * @param status 应该的状态
     * @return
     */
    private boolean checkXID(long xid, byte status) {
        long offset = getXidPosition(xid);
        ByteBuffer buffer = ByteBuffer.wrap(new byte[XID_FIELD_SIZE]);
        try {
            fc.position(offset);
            // 读取状态
            fc.read(buffer);
        } catch (IOException e) {
            Panic.panic(e);
        }
        return buffer.array()[0] == status;
    }

    @Override
    public boolean isActive(long xid) {
        // 超级事务, 就跑路
        if (xid == SUPER_XID) return false;
        return checkXID(xid, FIELD_TRAN_ACTIVE);
    }

    @Override
    public boolean isCommitted(long xid) {
        // 超级事务, 就rush
        if (xid == SUPER_XID) return true;
        return checkXID(xid, FIELD_TRAN_COMMITTED);
    }

    @Override
    public boolean isAborted(long xid) {
        // 超级事务, 就跑路
        if (xid == SUPER_XID) return false;
        return checkXID(xid, FIELD_TRAN_ABORTED);
    }

    @Override
    public void close() {
        try {
            fc.close();
            file.close();
        } catch (IOException e) {
            Panic.panic(e);
        }
    }
}
